#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

extern void escalaDeGrises(char* datos, unsigned filas, unsigned columnas);
extern void escalaDeGrises2(char* datos, unsigned filas, unsigned columnas);
extern void prewitt_asm(char* datos, char* copia, unsigned filas, unsigned columnas);
extern void sobel_asm(char* datos, char* copia, unsigned filas, unsigned columnas);
extern void roberts_asm(char* datos, char* copia, unsigned filas, unsigned columnas);
extern void umbralizar_asm(char* datos, unsigned filas, unsigned columnas, unsigned th, unsigned tl);

/* r1 = realzar bordes con el operador de Roberts.
   r2 = realzar bordes con el operador de Prewitt.
   r3 = realzar bordes con el operador de Sobel.
   m1 = monocromatizar con      = 1.
                              =∞
   m2 = monocromatizar con
   u = umbralizar (en este caso tambien toma dos enteros para los umbrales maximo y
   minimo)
   i = input (nombre del archivo)
*/

// TODO: completar este help y hacer que se imprima cuando hay un error.
void help(){
    printf("Parametros:\n");
    printf("--help            (-h) Muestra esta ayuda\n");
    printf("--m1              (-m) Convierte a escala de grises con");
    printf("promedios\n");
    printf("--m2              (-g) Convierte a escala de grises con maximo\n");
    printf("--input           (-i) Nombre de archivo de entrada");
    printf(" (obligatorio)\n");
    printf("--output          (-o) Nombre de archivo de salida");
    printf(" (obligatorio)\n");
    printf("--r1              (-r) Deteccion de bordes con el operador de");
    printf("Roberts\n");
    printf("--r2              (-p) Deteccion de bordes con el operador de");
    printf("Prewitt\n");
    printf("--r3              (-s) Deteccion de bordes con el operador de");
    printf("Sobel\n");
    printf("--umbralizar      (-u) Aplicar umbralizacion por histeresis. ");
    printf("Obligatorio: limite\n");
    printf("                  inferior y superior\n");
    printf("Para umbralizar es necesario seleccionar un filtro\n");
    printf("Por defecto se monocromatiza con promedios\n");
 }


int main(int argc, char **argv){
    const char* const op_cortas = "hmgrpsi:o:u:" ; // posibles parametros, van
//con -
    int siguiente_opcion;
    
    // forma larga para las opciones. En consola van con --  
    const struct option op_largas[] = {
      { "m1",         0, NULL,  'm'},
      { "m2",         0, NULL,  'g'},
      { "input",      1, NULL,  'i'},
      { "output",     1, NULL,  'm'},
      {"r1",          0, NULL,  'r'},
      {"r2",          0, NULL,  'p'},
      {"r3",          0, NULL,  's'},
      {"umbralizar",  1, NULL,  'u'},
      {"help",        0, NULL,  'h'},
      { NULL,         0, NULL,   0 }
    };

    // si no puso parametros, le decimos que necesitamos el archivo
    if (argc == 1) {
        printf("No se especifico una imagen!\n");
        help();
    }

    // declaracion de variables
    char * ficheroEntrada;      // nombre del archivo
    char * ficheroSalida = NULL;
    char metodo='m';            // metodo de monocromatizacion
    char metodoBordes = 'f';    // metodo de deteccion de bordes
    unsigned min, max;          // parametros para umbralizar
    int umbralizo = 0;          // flag de umbralizar
    
    while (1) {
        // llamamos a la función getopt
        siguiente_opcion = getopt_long (argc, argv, op_cortas, op_largas, NULL);

        if (siguiente_opcion == -1) // no hay mas opciones
            break; 

        // para cada parametro, seteamos el flag correspondiente
        switch (siguiente_opcion) {
            case 'm' : /* -m o --m1 */
                metodo = 'm' ;
                break;         
                      
            case 'g' : /* -g o --m2 */
                metodo = 'g' ;
                break;
  
           case 'r' : /* -r o --r1 */
                metodoBordes = 'r' ;
                break;
                   
           case 's' : /* -s o --r2 */
                metodoBordes = 's' ;
                break;
  
           case 'p' : /* -p o --r3 */
                metodoBordes = 'p' ;
                break;
  
           case 'u' : /* -u ó --umbralizar */
                umbralizo = 1;
                sscanf(optarg,"%d",&min);
                break;        
                  
           case 'i' : /* -i ó --input */
                ficheroEntrada = optarg; /* optarg contiene el argumento de -o */
                break;
          case 'o' : /* -o ó --output */
                ficheroSalida = optarg; /* optarg contiene el argumento de -o*/
                break;
          
          case 'h' : /* -h ó --help */
               help();
               return 0;
          
          case -1 : /* No hay más opciones */
                break;

          default : /* Algo más? No esperado. Abortamos */
                printf("Formato de opciones invalido!\n");
                help();
                return 1;
        }
    }

    // Para umbralizar se necesitan 2 parametros. Como getOpt solo toma uno, el otro lo coloca en
    // los no identificados. Para evitar que se pongan mas de uno, pedimos que solo halla un parametro no
    // identificado.

    if(umbralizo && optind == argc -1) 
        sscanf(argv[optind],"%d",&max);
        
    // si hay parametros de mas paramos
    if((!umbralizo && optind < argc) || (umbralizo && optind != argc -1) ) {
        printf("Formato de opciones invalido!\n");
        help();
        return 1;
    }


    // abrimos el archivo
    FILE* fichero;
    FILE* salida;
    int ancho, alto, offset;
    char marca1, marca2;
     if (ficheroSalida == NULL) {
        puts("No se pudo crear archivo de salida\n");
        return 1;
    }
    fichero = fopen(ficheroEntrada, "rbw+");
    salida = fopen(ficheroSalida, "wb");
    if (fichero == NULL) {
        puts("Archivo no encontrado! Ingreso el nombre correcto?\n");
        help();
        return 1;
    }
     if (salida == NULL) {
        puts("No se pudo crear archivo de salida\n");
        return 1;
    }
    else {
        marca1 = fgetc(fichero);  // leemos los dos primeros bytes
        marca2 = fgetc(fichero);
        if (marca1 =='B' && marca2 =='M') {     // si son BM, sino paramos
            
            fseek(fichero, 18, SEEK_SET);       // posicion 18: ancho
            fread(&ancho, 1, 4, fichero);
            fread(&alto, 1, 4, fichero);        // siguiente dato: alto
            fseek(fichero, 10, SEEK_SET);       // tomamos el offset hasta los pixeles
            fread(&offset,1,4,fichero);
            
            short int sumandoextra = (4 - (ancho*3 % 4)) % 4;
            unsigned int dimension = (ancho * 3 + sumandoextra) * alto;  // cada pixel son 24 bits, o sea 3 bytes
            char * matriz = (char*) malloc(dimension);  // memoria para levantar el archivo
            char * matriz2 = (char*) malloc(dimension); // memoria para guardar los bordes detectados
            
            if(matriz != 0) {
                fseek(fichero, offset, SEEK_SET);
                fread(matriz, dimension, 1, fichero);

                // operamos segun los parametros recibidos
                if(metodo == 'm'){
                    printf("Monocromatizando...\n");
                    escalaDeGrises(matriz, alto, 3*ancho);

                }
                else if(metodo == 'g'){
                    printf("Monocromatizando...\n");
                    escalaDeGrises2(matriz, alto, 3*ancho);
                }

                if(metodoBordes  == 's') {
                     printf("Aplicando Sobel...\n");

                     sobel_asm(matriz, matriz2, alto, ancho);
                }
                else if (metodoBordes == 'r') {
                    printf("Aplicando Roberts...\n");
                    roberts_asm(matriz, matriz2, alto, ancho);
                }
                else if (metodoBordes == 'p'){ 
                    printf("Aplicando Prewitt...\n");
                    prewitt_asm(matriz, matriz2, alto, ancho);
                }
                 if(umbralizo && metodoBordes == 'f'){
                    printf("Para umbralizar se debe aplicar un filtro primero");
                    printf("\nSeleccione un filtro para poder umbralizar\n");
                    help();
                    umbralizo = 0;      
                }  
                if(umbralizo) { // llamada a umbralizar
                                    
                     if(min > 255){
                        printf("Adevertencia: los valores mayores a 255, seran interpretados como 255.\n");
                        min = 255;
                    }  
                    if(max > 255){
                        printf("Adevertencia: los valores mayores a 255, seran interpretados como 255.\n");
                        max = 255;
                    }  

                    printf("Umbralizando con umbral superior %u y umbral inferior %u ...\n", max, min);
                    umbralizar_asm(matriz2,alto, ancho, max, min);
                }
                char * info = (char*) malloc(offset);
                fseek(fichero, 0, SEEK_SET);
                fread(info,offset,1,fichero);
                fwrite(info, offset, 1, salida);
                fseek(fichero, offset, SEEK_SET);
                if(metodoBordes != 'f'){
                    fwrite(matriz2, dimension, 1, salida);
                }
                else{
                    fwrite(matriz, dimension, 1, salida);
                }
                fclose(fichero);
                fclose(salida);
                free(matriz);
                free(matriz2);
                free(info);
            }
        }

        return 0;
    }
}

